/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pe.edu.pucp.sigepa.recocidosimulado.manager;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.javatuples.Pair;
import org.joda.time.DateTime;
import org.joda.time.Period;
import pe.edu.pucp.sigepa.recocidosimulado.Modelo.VueloSim;
import pe.edu.pucp.sigepa.recocidosimulado.capadatos.impl.LogicaAeropuertosSimulacionImpl;
import pe.edu.pucp.sigepa.recocidosimulado.capadatos.impl.LogicaVuelosSimulacionImpl;
import pe.edu.pucp.sigepa.recocidosimulado.domain.interfaz.ProblemaSim;
import pe.edu.pucp.sigepa.recocidosimulado.domain.interfaz.SolucionSim;
import pe.edu.pucp.sigepa.recocidosimulado.random.Dados;
import pe.edu.pucp.sigepa.recocidosimulado.util.EsquemaEnfriamientoUtilitario;


/**
 *
 * @author carloslengua
 */

public class GestorRecocidoSimuladoSimulacion {
    
    public static final Integer HACE_CALOR_HACE_CALOR   = 500;
    public static final Integer UN_VERANO_COMO_TODOS    = 501;
    public static final Integer COMO_OSO_POLAR          = 502;
    
    private static final Double PLAZO_MAXIMO = Double.valueOf(86400);
    
    private static final Integer MAX_NUM_INTENTOS = 100;
    
    private ProblemaSim problemaSim;
    private Integer esquemaDeEnfriamiento;
    private SolucionSim solucionSim;
    
    
    public GestorRecocidoSimuladoSimulacion(ProblemaSim problemaSim, Integer esquemaDeEnfriamiento) {
        this.problemaSim = problemaSim;
        this.esquemaDeEnfriamiento = esquemaDeEnfriamiento;
        this.solucionSim = null;
    }

    public ProblemaSim getProblema() {
        return problemaSim;
    }

    public void setProblema(ProblemaSim problema) {
        this.problemaSim = problema;
    }
    
    public void establecerEsquemaDeEnfriamento(Integer esquemaDeEnfriamiento) {
        this.esquemaDeEnfriamiento = esquemaDeEnfriamiento;
    }
    
    public SolucionSim leerSolucion() {
        return solucionSim;
    }
    
    public void iniciar() {
    
        SolucionSim solucionSimActual;
        SolucionSim solucionSimSiguiente;
        Double temperatura;
        
        List<Double> esquema = fabricarEsquema();
        
        solucionSimActual = hacerNodo(estadoInicial());
        
        this.solucionSim = solucionSimActual;
//        return;
    
        if (solucionSimActual == null) {
            this.solucionSim = null;
            return;
        }
        
        Integer t;
        Double deltaDeE;
        Double x;
        
        for (t = 0; t < 2000000000; t++) {
            temperatura = esquema.get(t);
            //Depuracion
            System.out.println("Temperatura = " + temperatura);
            if (temperatura == 0) {
                this.solucionSim = solucionSimActual;
                return;            
            }
            
            solucionSimSiguiente = seleccionarSucesorAleatorio(problemaSim, solucionSimActual);
            
            
            deltaDeE = problemaSim.calcularValor(solucionSimSiguiente) - problemaSim.calcularValor(solucionSimActual);
            
            
            if (deltaDeE < 0) {
                solucionSimActual = solucionSimSiguiente;
            } else {
                x = Math.random();
                if ( esMomentoDeEmpeorarLaSolucion(-deltaDeE, temperatura, x) ) {
                    solucionSimActual = solucionSimSiguiente;
                }
            }
        }
        
    }
    
    private List<Double> fabricarEsquema() {
    
        List<Double> esquema = new ArrayList<>();
        
        if (esquemaDeEnfriamiento == GestorRecocidoSimuladoSimulacion.HACE_CALOR_HACE_CALOR) {
            esquema.add(Double.valueOf(999999));
            esquema.add(Double.valueOf(999800));
            esquema.add(Double.valueOf(998000));
            esquema.add(Double.valueOf(950000));
            esquema.add(Double.valueOf(949000));
            esquema.add(Double.valueOf(900000));
            esquema.add(Double.valueOf(750000));
            esquema.add(Double.valueOf(500000));
            esquema.add(Double.valueOf(250000));
            esquema.add(Double.valueOf(0));
        
        }
        
        if (esquemaDeEnfriamiento == GestorRecocidoSimuladoSimulacion.UN_VERANO_COMO_TODOS) {

            esquema = EsquemaEnfriamientoUtilitario.retornarEsquemaEnfriamentoUnVeranoComoTodos_Breve();
        
        }
        
        if (esquemaDeEnfriamiento == GestorRecocidoSimuladoSimulacion.COMO_OSO_POLAR) {
            esquema.add(Double.valueOf(999999));
            esquema.add(Double.valueOf(500000));
            esquema.add(Double.valueOf(450000));
            esquema.add(Double.valueOf(400000));
            esquema.add(Double.valueOf(350000));
            esquema.add(Double.valueOf(300000));
            esquema.add(Double.valueOf(250000));
            esquema.add(Double.valueOf(200000));
            esquema.add(Double.valueOf(100000));
            esquema.add(Double.valueOf(0));
        
        }
        
        return esquema;
    
    
    }//OK
    
    private SolucionSim seleccionarSucesorAleatorio(ProblemaSim problemaSim, SolucionSim solucionSim) {
        
//        throw new UnsupportedOperationException("Not supported yet.");
    
        Integer numeroAleatorio_0_o_1 = (new Dados()).lanzar() % 2;       
        
        if (solucionSim.size() == 1) {
            numeroAleatorio_0_o_1 = 1;
        }
        
        SolucionSim solucionSimSucesora = new SolucionSim(solucionSim);        
        
        if (numeroAleatorio_0_o_1 == 0) {
            
            //Obviar una escala
            
            //Depuracion:
            System.out.println("Obviando una escala...");
            
            Integer posx = (new Dados()).lanzar() % (solucionSim.size() - 1) ;
            
//            Pair<Vuelo,Vuelo> parDeVuelosADirectear = new Pair<>(solucion.get(posx), solucion.get(posx + 1));
            
            List<VueloSim> vuelosReemplazantes = (new LogicaVuelosSimulacionImpl()).obtenerVuelosReemplazantes(problemaSim, solucionSim, posx);
            
            if (vuelosReemplazantes.isEmpty()) {
                //Depuracion:
                System.out.println("Operacion sin exito...");                 
                return solucionSim;
            }
            
            //Depuracion:
            System.out.println("Operacion CON exito... Procediendo a generar la solucion vecina");              
            System.out.println("SolucionSim:        " + solucionSim);
            
            Integer posVueloReemplazante = (new Dados()).lanzar() % vuelosReemplazantes.size();
            
            VueloSim vueloSimReemplazante = vuelosReemplazantes.get(posVueloReemplazante);
            
            solucionSimSucesora = solucionSimSucesora.reemplazarEscalaPorDirecto(posx, vueloSimReemplazante);
            
            System.out.println("SolucionSim vecina: " + solucionSimSucesora);
            
            
        } else {
            
            //Agregar una escala
            
            //Depuracion:
            System.out.println("Agregando una escala...");            
            
            Integer posx = (new Dados()).lanzar() % solucionSim.size();
            
//            Vuelo vueloX = solucion.get(posx);
            
            List<Pair<VueloSim,VueloSim>> listaDeParesDeVuelosSim = (new LogicaVuelosSimulacionImpl()).retornarParDeVuelosAReemplazar(problemaSim, solucionSim, posx);
            
            if (listaDeParesDeVuelosSim.isEmpty()) {
                //Depuracion:
                System.out.println("Operacion sin exito...");                
                return solucionSim;
            }
            
            //Depuracion:
            System.out.println("Operacion CON exito... Procediendo a generar la solucion vecina");   
            System.out.println("SolucionSim:        " + solucionSim);
            
            Integer parElegido = (new Dados()).lanzar() % listaDeParesDeVuelosSim.size();
            
            Pair<VueloSim,VueloSim> parDeVuelos = listaDeParesDeVuelosSim.get(parElegido);
            
            solucionSimSucesora = solucionSimSucesora.reemplazarVueloDirectoPorEscalado(posx, parDeVuelos);
            System.out.println("SolucionSim vecina: " + solucionSimSucesora);
            
        }
        
        
        if (! excedePlazo(problemaSim, solucionSimSucesora.get(solucionSimSucesora.size()-1).getLlegadaest())) {
            return solucionSimSucesora;
        } else {
            System.out.println("La solucion vecina no cumple con las restricciones de plazo");  
            return solucionSim;
        }
        
    }
    
    private boolean esMomentoDeEmpeorarLaSolucion(Double deltaDeE, Double temperatura, Double x) {
    
        //Lanzar esfera
        Double posicionEsfera = x;
        
        Double vallaDerecha = Double.valueOf(0.00);
        Double vallaIzquierda = Math.exp(deltaDeE / temperatura);
        
        System.out.println("posicionEsfera = " + posicionEsfera);
        System.out.println("vallaDerecha = " + vallaDerecha);
        System.out.println("vallaIzquierda = " + vallaIzquierda);
        
        if (vallaDerecha <= posicionEsfera && posicionEsfera <= vallaIzquierda) {
            return true;
        } else {
            return false;
        }
    
    }
    
    private SolucionSim estadoInicial() {
    
        SolucionSim solucionSemilla = new SolucionSim();
        Integer idAeropuertoActual = null;
        Integer idAeropuertoOrigen = problemaSim.getIdAeropuertoOrigen();
        Integer idAeropuertoDestino = problemaSim.getIdAeropuertoDestino();
        Integer numIntentos = 0;
        Date fechaHoraSimulacion;
        
        if ( !(new LogicaAeropuertosSimulacionImpl()).brindaAbasto(idAeropuertoOrigen, problemaSim.getFechaPeticion(), problemaSim.getNroPaquetes())) {
            return null;
        }
            
        do {
            
            solucionSemilla = new SolucionSim();
            
            List<Integer> visitados = new ArrayList<>();

            visitados.add(idAeropuertoOrigen);

            idAeropuertoActual = idAeropuertoOrigen;
            
            fechaHoraSimulacion = problemaSim.getFechaPeticion();
            
            //Depuracion
            System.out.print("Si es posible iniciar el proceso... - ");
            System.out.println("Fecha y hora de inicio: " + fechaHoraSimulacion);

            while (idAeropuertoActual != idAeropuertoDestino) {
                
                List<VueloSim> vuelosSim = (new LogicaVuelosSimulacionImpl()).retornarVuelos(idAeropuertoActual, fechaHoraSimulacion, problemaSim.getNroPaquetes());
                
                
                
                List<VueloSim> vuelosSim_temp = new ArrayList<>();
                
                for (VueloSim vueloSim : vuelosSim) {
                
                    if (!visitados.contains(vueloSim.getAeropuertoSimDestino().getIdaeropuerto())) {
                        vuelosSim_temp.add(vueloSim);
                    }
                    
                }
                
                vuelosSim = vuelosSim_temp;
                
                
                
                if (vuelosSim.isEmpty()) {
                    break;
                }
                
                Integer indiceVueloAleatorio = (new Dados()).lanzar() % vuelosSim.size();
                
                VueloSim vueloSimSeleccionado = vuelosSim.get(indiceVueloAleatorio);
                
                solucionSemilla.add(vueloSimSeleccionado);
                
                idAeropuertoActual = vueloSimSeleccionado.getAeropuertoSimDestino().getIdaeropuerto();
                
                visitados.add(idAeropuertoActual);
                
                fechaHoraSimulacion = vueloSimSeleccionado.getLlegadaest();
                
                //Depuracion
                System.out.println(vueloSimSeleccionado);

            }
            
            if (idAeropuertoActual != idAeropuertoDestino) {
                //En este punto, el intento de generar una solución semilla fue un fracaso porque el último nodo visitado era un "callejon sin salida".
                //Estas instrucciones aqui abajo tienen el objetivo de que el ordenador vuelva a intentar nuevamente.
                
                System.out.println("CALLEJON SIN SALIDA DETECTADO: REARRANCANDO PROCESO...");
                
                Calendar calendario = Calendar.getInstance();
                
                calendario.set(2012, 10, 31, 23, 59, 59);
                                
                fechaHoraSimulacion = calendario.getTime();
                
                numIntentos++;
                
                System.out.println("Numero de intentos = " + numIntentos);
                
                if (numIntentos >= GestorRecocidoSimuladoSimulacion.MAX_NUM_INTENTOS) {
                    System.out.println("Numero de intentos máximos agotados... Se aborta el proceso");
                    return null;
                }
                
//                continue;
            }
            
        } while (excedePlazo(problemaSim, fechaHoraSimulacion));
        
        return solucionSemilla;
    
    }//OK
    
    private Boolean excedePlazo(ProblemaSim problema, Date fechaHoraSimulacion) {
    
        Integer idContinenteOrigen = (new LogicaVuelosSimulacionImpl()).obtenerIdContinente(problema.getIdAeropuertoOrigen());
        Integer idContinenteDestino = (new LogicaVuelosSimulacionImpl()).obtenerIdContinente(problema.getIdAeropuertoDestino());
        
        if (idContinenteOrigen == idContinenteDestino) {
            
            DateTime tiempoFin = new DateTime(fechaHoraSimulacion);
            DateTime tiempoInicio = new DateTime(problema.getFechaPeticion());
            
            Period p = new Period(tiempoInicio, tiempoFin);
            
            Long nroHorasEntreFechas = (fechaHoraSimulacion.getTime() - problema.getFechaPeticion().getTime())/(60*60 * 1000); 
            
            if (nroHorasEntreFechas > ProblemaSim.PLAZO_CONTINENTAL) {            
                return true;
            } else {
                return false;
            }
        
        } else {
            
            DateTime tiempoFin = new DateTime(fechaHoraSimulacion);
            DateTime tiempoInicio = new DateTime(problema.getFechaPeticion());
            
            Period p = new Period(tiempoInicio, tiempoFin);
            
            Long nroHorasEntreFechas = (fechaHoraSimulacion.getTime() - problema.getFechaPeticion().getTime())/(60*60 * 1000); 
            
            if (nroHorasEntreFechas > ProblemaSim.PLAZO_INTERCONTINENTAL) {
                return true;
            } else {
                return false;
            }
        
        }
        
    }
    
    private SolucionSim hacerNodo(SolucionSim solucion) {
        return solucion;
    }
    
    
}
